package com.cassioso.redeneural.head;

import java.util.Random;

import com.cassioso.redeneural.memoria.Forma;
import com.cassioso.redeneural.memoria.M;
import com.cassioso.redeneural.memoria.UtilM;


/**
 * Classe que realiza a sinapse
 */
public class Cerebro {

	/**
	 * Representa a lista de Neuronios para primeira camada
	 */
	private Neuronio camadaEntrada[];

	/**
	 * Representa a lista de Neuronios para segunda camada
	 */
	private Neuronio camadaIntermediaria[];

	/**
	 * Representa a lista de Neuronios para segunda camada
	 */
	private Neuronio camadaSaida[];

	/**
	 * Representa o erro global, calculado a partir das saídas de cada neurônio
	 * e a saída esperada
	 */
	private double emq[];

	private final static int NUMERO_INTER = 50;

	private final static int NUMERO_INI = 100;

	/**
	 * Representa a taxa de aprendizado.
	 */
	private final double TAXA_APRENDIZADO = 0.5;

	/**
	 * Método construtor. 
	 */
	public Cerebro() {
		this.camadaEntrada = new Neuronio[100];
		this.camadaIntermediaria = new Neuronio[50];
		this.camadaSaida = new Neuronio[5];
		this.emq = new double[6];
	}

	/**
	 * Retorna a taxa de aprendizado
	 */
	public double getTxAprendizado() {
		return TAXA_APRENDIZADO;
	}
	
	/**
	 * Carrega a camada de entrada com os dados da forma passada como parametro.
	 */
	public void preencheCamadaEntrada(Forma forma) {
		// Para cada posição de entrada do neurônio
		for (int i = 0; i < this.camadaEntrada.length; i++) {
			// Para cada linha da matriz de valores...
			for (int l = 0; l < 10; l++) {
				// Para cada coluna da linha da matriz de vetores...
				for (int c = 0; c < 10; c++) {
//					System.out.println("Cerebro.preencheCamadaEntrada() " + this.camadaEntrada[i].getSaida());
					this.camadaEntrada[i] = new Neuronio();
					this.camadaEntrada[i].setSaida(forma.getValor()[l][c]);
					i++;
				}
			}
		}
	}

	/**
	 * Define os pesos iniciais
	 */
	public void setarPesoInicio() {
		for (int i = 0; i < this.camadaIntermediaria.length; i++) {
			this.camadaIntermediaria[i].setListaPeso(this.gerarPesosRandom(Cerebro.NUMERO_INI));
		}
		for (int i = 0; i < this.camadaSaida.length; i++) {
			this.camadaSaida[i].setListaPeso(this.gerarPesosRandom(Cerebro.NUMERO_INTER));
		}
	}

	/**
	 *  Calcula as saidas
	 */
	public void realizaIda() {
		for (int i = 0; i < this.camadaIntermediaria.length; i++) {
			this.camadaIntermediaria[i].fazSomatorio(this.camadaEntrada);
		}
		for (int i = 0; i < this.camadaSaida.length; i++) {
			this.camadaSaida[i].fazSomatorio(this.camadaSaida);
		}
	}

	/**
	 *  Calcula os erros e redefine os pesos
	 */
	public void realizaVolta(int desejada[]) {
		// Define erros....................
		for (int i = 0; i < this.camadaSaida.length; i++) {
			double erro;
			erro = (desejada[i] - this.camadaSaida[i].getSaida())
					* (1 - Math.pow(this.camadaSaida[i].getSaida(), 2));
			this.camadaSaida[i].setErro(erro);
		}
		for (int i = 0; i < this.camadaIntermediaria.length; i++) {
			double somatorio = 0;
			for (int j = 0; j < this.camadaSaida.length; j++) {
				somatorio = this.camadaSaida[j].getErro()
						* this.camadaSaida[j].getListaPeso()[i];
			}
			this.camadaIntermediaria[i]
					.setErro(somatorio
							* (1 - Math.pow(this.camadaIntermediaria[i]
									.getSaida(), 2)));
		}

		// Define novos pesos...............
		for (int s = 0; s < this.camadaSaida.length; s++) {
			for (int p = 0; p < this.camadaSaida[p].getListaPeso().length; p++) {
				double w = this.camadaSaida[p].getListaPeso()[p];
				double e = this.camadaSaida[s].getErro();
				double x = this.camadaIntermediaria[p].getSaida();
				this.camadaSaida[p].getListaPeso()[p] = w
						+ this.getTxAprendizado() * e * x;
			}
		}

		for (int s = 0; s < this.camadaIntermediaria.length; s++) {
			for (int p = 0; p < this.camadaIntermediaria[p].getListaPeso().length; p++) {
				double w = this.camadaIntermediaria[p].getListaPeso()[p];
				double e = this.camadaIntermediaria[s].getErro();
				double x = this.camadaEntrada[p].getSaida();
				this.camadaIntermediaria[p].getListaPeso()[p] = w
						+ this.getTxAprendizado() * e * x;
			}
		}
	}

	/**
	 * Gera pesos aleatórios
	 */
	public double[] gerarPesosRandom(int tam) {
		double retorno[] = new double[tam];
		Random r = new Random();
		int temp;
		for (int i = 0; i < tam; i++) {
			temp = 1;
			if (r.nextBoolean())
				temp = -1;
			retorno[i] = Math.random() * temp;
		}
		return retorno;
	}

	/**
	 * Calcula o EMQ médio
	 */
	public double getEmqMedio() {
		double emqMedio = 0;
		for (int i = 0; i < this.emq.length; i++) {
			emqMedio += this.emq[i];
		}
		return emqMedio / this.emq.length;
	}

	/**
	 *  Retorna a lista de saídas...
	 */
	public double[] getSaida() {
		double saida[] = new double[5];
		for (int i = 0; i < this.camadaSaida.length; i++) {
			saida[i] = this.camadaSaida[i].getSaida();
		}
		return saida;
	}

	public static void main(String args[]) {
		Cerebro c = new Cerebro();
		c.setarPesoInicio();
		// Carrega formas do arquivo..
		Forma fc[] = M.lembrar();
		double emq = 1000;
		
		for (int ciclo = 0; (ciclo < 1000) && (emq > 0.005); ciclo++) {
			for (int f = 0; f < fc.length; f++) {
				c.preencheCamadaEntrada(fc[f]);
				c.realizaIda();
				c.realizaVolta(UtilM.getGabarito(f));
			}
			emq = c.getEmqMedio();
		}
	}
}